﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Tesla.Math;

namespace Tesla.Content.Loaders {
    /// <summary>
    /// Savable writer that serializes ISavable objects into a Tesla Engine Binary Object (TEBO)
    /// file format.
    /// </summary>
    public sealed class BinarySavableWriter : ISavableWriter {
        private static readonly Dictionary<Object, bool> _recurseDetection = new Dictionary<Object, bool>();

        private BinaryWriter _output;
        private bool _isDisposed = false;
        private bool _writeExternals;
        private String _resourceName;
        private Dictionary<Type, ExternalWriter> _externalWriters;
        private const sbyte NULL_OBJECT = -1;
        private const sbyte A_OK = 0;

        /// <summary>
        /// Gets if the writer has been disposed or not.
        /// </summary>
        public bool IsDisposed {
            get {
                return _isDisposed;
            }
        }

        /// <summary>
        /// Gets or sets if the writer should write external objects, which involves evoking
        /// writers for that type. If false, externals are simply written to the same output.
        /// </summary>
        public bool WriteExternals {
            get {
                return _writeExternals;
            }
            set {
                _writeExternals = value;
            }
        }

        /// <summary>
        /// Gets the name of the resource output that the writer will write data to.
        /// </summary>
        public String FullResourceName {
            get {
                return _resourceName;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BinarySavableWriter"/> class. External
        /// objects will be written directly to the stream.
        /// </summary>
        /// <param name="outputStream">The stream to write the output to.</param>
        public BinarySavableWriter(Stream outputStream) {
            _output = new BinaryWriter(outputStream);
            _writeExternals = false;
            _resourceName = null;
            _externalWriters = new Dictionary<Type, ExternalWriter>();
            SetDefaultWriters();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BinarySavableWriter"/> class. External
        /// objects will be written to individual files, unless if write externals is set to false
        /// or no resource name is given.
        /// </summary>
        /// <param name="outputStream">The stream to write the output to.</param>
        /// <param name="resourceName">Full file path</param>
        public BinarySavableWriter(Stream outputStream, String resourceName) {
            _output = new BinaryWriter(outputStream);
            if(resourceName != null) {
                _writeExternals = true;
                if(!Path.IsPathRooted(resourceName)) {
                    throw new ArgumentException("resourceName", "Must supply a fully qualified file path.");
                }
            } else {
                _writeExternals = false;
            }
            _resourceName = resourceName;
            _externalWriters = new Dictionary<Type, ExternalWriter>();
            SetDefaultWriters();
        }

        private void SetDefaultWriters() {
            _externalWriters.Clear();
            RegisterExternalWriter(new BinaryExternalWriter());
            RegisterExternalWriter(new TextureBinaryExternalWriter());
            RegisterExternalWriter(new EffectExternalWriter());
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="BinarySavableWriter"/> is reclaimed by garbage collection.
        /// </summary>
        ~BinarySavableWriter() {
            Dispose(false);
        }

        /// <summary>
        /// Registers an external writer to be used by the savable writer when an external
        /// reference is asked to be written for a specific savable type.
        /// </summary>
        /// <param name="writer">External reader</param>
        public void RegisterExternalWriter(ExternalWriter writer) {
            _externalWriters[writer.TargetType] = writer;
            writer.Initialize(this);
        }

        /// <summary>
        /// Gets an external writer, if it exists, by target type.
        /// </summary>
        /// <param name="targetType">The savable target type.</param>
        /// <returns>
        /// The appropiate writer
        /// </returns>
        public ExternalWriter GetExternalWriter(Type targetType) {
            ExternalWriter writer;
            if(targetType != null && _externalWriters.TryGetValue(targetType, out writer)) {
                return writer;
            }
            return null;
        }

        /// <summary>
        /// Removes an external writer by target type.
        /// </summary>
        /// <param name="targetType">Savable target type</param>
        public void RemoveExternalWriter(Type targetType) {
            _externalWriters.Remove(targetType);
        }

        /// <summary>
        /// Causes all outstanding data to be written to their outputs.
        /// </summary>
        public void Flush() {
            foreach(KeyValuePair<Type, ExternalWriter> kv in _externalWriters) {
                kv.Value.Flush();
            }
        }

        /// <summary>
        /// Writes a single byte to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Byte value</param>
        public void Write(string name, byte value) {
            _output.Write(value);
        }

        /// <summary>
        /// Writes an array of bytes to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of bytes</param>
        public void Write(string name, byte[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            _output.Write(value.Length);
            _output.Write(value);
        }

        /// <summary>
        /// Writes a 2D array of bytes to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of bytes</param>
        public void Write(String name, byte[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    _output.Write(value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single sbyte to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">SByte value</param>
        public void Write(string name, sbyte value) {
            _output.Write(value);
        }

        /// <summary>
        /// Writes an array of sbytes to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of sbytes</param>
        public void Write(string name, sbyte[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                _output.Write(value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of sbytes to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of sbytes</param>
        public void Write(string name, sbyte[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    _output.Write(value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single char to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Char value</param>
        public void Write(string name, char value) {
            _output.Write(value);
        }

        /// <summary>
        /// Writes an array of char to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of char</param>
        public void Write(string name, char[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                _output.Write(value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of char to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of char</param>
        public void Write(string name, char[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    _output.Write(value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single unsigned 16-bit int to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">UInt16 value</param>
        public void Write(string name, ushort value) {
            _output.Write(value);
        }

        /// <summary>
        /// Writes an array of unsigned 16-bit ints to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of UInt16</param>
        public void Write(string name, ushort[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                _output.Write(value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of unsigned 16-bit ints to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of UInt16</param>
        public void Write(string name, ushort[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    _output.Write(value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single unsigned 32-bit int to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">UInt32 value</param>
        public void Write(string name, uint value) {
            _output.Write(value);
        }

        /// <summary>
        /// Writes an array of unsigned 32-bit ints to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of UInt32</param>
        public void Write(string name, uint[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                _output.Write(value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of unsigned 32-bit ints to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of UInt32</param>
        public void Write(string name, uint[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    _output.Write(value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single unsigned 64-bit int to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">UInt64 value</param>
        public void Write(string name, ulong value) {
            _output.Write(value);
        }

        /// <summary>
        /// Writes an array of unsigned 64-bit ints to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of UInt64</param>
        public void Write(string name, ulong[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                _output.Write(value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of unsigned 64-bit ints to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of UInt64</param>
        public void Write(string name, ulong[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    _output.Write(value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single 16-bit int to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Int16 value</param>
        public void Write(string name, short value) {
            _output.Write(value);
        }

        /// <summary>
        /// Writes an array of 16-bit ints to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of Int16</param>
        public void Write(string name, short[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                _output.Write(value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of 16-bit ints to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of Int16</param>
        public void Write(string name, short[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    _output.Write(value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single 32-bit int to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Int32 value</param>
        public void Write(string name, int value) {
            _output.Write(value);
        }

        /// <summary>
        /// Writes an array of 32-bit ints to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of Int32</param>
        public void Write(string name, int[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                _output.Write(value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of 32-bit ints to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of Int32</param>
        public void Write(string name, int[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    _output.Write(value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single 64-bit int to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Int64 value</param>
        public void Write(string name, long value) {
            _output.Write(value);
        }

        /// <summary>
        /// Writes an array of 64-bit ints to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of Int64</param>
        public void Write(string name, long[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                _output.Write(value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of 64-bit ints to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of Int64</param>
        public void Write(string name, long[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    _output.Write(value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single float value to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Float value</param>
        public void Write(string name, float value) {
            _output.Write(value);
        }

        /// <summary>
        /// Writes an array of floats to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of floats</param>
        public void Write(string name, float[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                _output.Write(value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array floats to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of floats</param>
        public void Write(string name, float[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    _output.Write(value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single double value to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Double value</param>
        public void Write(string name, double value) {
            _output.Write(value);
        }

        /// <summary>
        /// Writes an array of doubles to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of doubles</param>
        public void Write(string name, double[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                _output.Write(value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of doubles to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of doubles</param>
        public void Write(string name, double[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    _output.Write(value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single boolean to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Boolean value</param>
        public void Write(string name, bool value) {
            _output.Write(value);
        }

        /// <summary>
        /// Writes an array of booleans to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of booleans</param>
        public void Write(string name, bool[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                _output.Write(value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of booleans to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of booleans</param>
        public void Write(string name, bool[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    _output.Write(value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single string to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">String value</param>
        public void Write(string name, string value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            _output.Write(value);
        }

        /// <summary>
        /// Writes an array of strings to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of strings</param>
        public void Write(string name, string[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                _output.Write(value[i]);
            }
        }

        /// <summary>
        /// WRites a 2D array of strings to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of strings</param>
        public void Write(string name, string[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    _output.Write(value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single color struct to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Color value</param>
        public void Write(string name, Color value) {
            _output.Write(value.PackedValue);
        }

        /// <summary>
        /// Writes an array of color structs to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of colors</param>
        public void Write(string name, Color[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                this.Write(null, value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of color structs to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of colors</param>
        public void Write(string name, Color[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    this.Write(null, value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single matrix struct to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Matrix value</param>
        public void Write(string name, Matrix value) {
            _output.Write(value.M11);
            _output.Write(value.M12);
            _output.Write(value.M13);
            _output.Write(value.M14);

            _output.Write(value.M21);
            _output.Write(value.M22);
            _output.Write(value.M23);
            _output.Write(value.M24);

            _output.Write(value.M31);
            _output.Write(value.M32);
            _output.Write(value.M33);
            _output.Write(value.M34);

            _output.Write(value.M41);
            _output.Write(value.M42);
            _output.Write(value.M43);
            _output.Write(value.M44);
        }

        /// <summary>
        /// Writes an array of matrix structs to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of matrices</param>
        public void Write(string name, Matrix[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                this.Write(null, value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of matrix structs to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of matrices</param>
        public void Write(string name, Matrix[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    this.Write(null, value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single quaternion struct to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Quaternion value</param>
        public void Write(string name, Quaternion value) {
            _output.Write(value.X);
            _output.Write(value.Y);
            _output.Write(value.Z);
            _output.Write(value.W);
        }

        /// <summary>
        /// Writes an array of quaternion structs to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of quaternions</param>
        public void Write(string name, Quaternion[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                this.Write(null, value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of quaternion structs to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of quaternions</param>
        public void Write(string name, Quaternion[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    this.Write(null, value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single vector2 struct to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Vector2 value</param>
        public void Write(string name, Vector2 value) {
            _output.Write(value.X);
            _output.Write(value.Y);
        }

        /// <summary>
        /// Writes an array of vector2 structs to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of vector2</param>
        public void Write(string name, Vector2[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                this.Write(null, value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of vector2 structs to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of vector2</param>
        public void Write(string name, Vector2[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    this.Write(null, value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single vector3 struct to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Vector3 value</param>
        public void Write(string name, Vector3 value) {
            _output.Write(value.X);
            _output.Write(value.Y);
            _output.Write(value.Z);
        }

        /// <summary>
        /// Writes an array of vector3 structs to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of vector3</param>
        public void Write(string name, Vector3[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                this.Write(null, value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of vector3 structs to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of vector3</param>
        public void Write(string name, Vector3[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    this.Write(null, value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a single vector4 struct to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Vector4 value</param>
        public void Write(string name, Vector4 value) {
            _output.Write(value.X);
            _output.Write(value.Y);
            _output.Write(value.Z);
            _output.Write(value.W);
        }

        /// <summary>
        /// Writes an array of vector4 structs to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of vector4</param>
        public void Write(string name, Vector4[] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                this.Write(null, value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of vector4 structs to the output.
        /// </summary>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of vector4</param>
        public void Write(string name, Vector4[,] value) {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }
            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    this.Write(null, value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes an enum value to the output.
        /// </summary>
        /// <typeparam name="T">Enum type</typeparam>
        /// <param name="name">Name of the value</param>
        /// <param name="enumeration">Enum value</param>
        public void WriteEnum<T>(String name, T enumeration) where T : struct {
            this.Write(name, enumeration.ToString());
        }

        /// <summary>
        /// Writes a savable object to the output.
        /// </summary>
        /// <typeparam name="T">Type of object to write</typeparam>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Savable object</param>
        public void WriteSavable<T>(string name, T value) where T : ISavable {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                if(!(value is T)) {
                    throw new InvalidCastException("Cannot write savable as the requested type.");
                }
                if(DetectRecurse(value)) {
                    throw new InvalidOperationException("Cyclic reference detected.");
                }
                _output.Write(A_OK);
            }

            this.Write(null, value.GetType().AssemblyQualifiedName);

            AddRecurse(value);
            value.Write(this);
            RemoveRecurse(value);
        }

        /// <summary>
        /// Writes an array of savable objects to the output.
        /// </summary>
        /// <typeparam name="T">Type of object to write</typeparam>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Array of savables</param>
        public void WriteSavable<T>(string name, T[] value) where T : ISavable {
            if(value == null || value.Length == 0) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }

            int num = value.Length;
            _output.Write(num);
            for(int i = 0; i < value.Length; i++) {
                this.WriteSavable<T>(null, value[i]);
            }
        }

        /// <summary>
        /// Writes a 2D array of savable objects to the output.
        /// </summary>
        /// <typeparam name="T">Type of object to write</typeparam>
        /// <param name="name">Name of the value</param>
        /// <param name="value">2D array of savables</param>
        public void WriteSavable<T>(string name, T[,] value) where T : ISavable {
            if(value == null || value.Length == 0) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                _output.Write(A_OK);
            }

            int row = value.GetUpperBound(0);
            int col = value.GetUpperBound(1);
            _output.Write(row);
            _output.Write(col);
            for(int i = 0; i < row; i++) {
                for(int j = 0; j < col; j++) {
                    this.WriteSavable<T>(null, value[i, j]);
                }
            }
        }

        /// <summary>
        /// Writes a reference to another savable output that is external to this output. Externals
        /// may or may not be written separately, such as there exists no writer for the savable type.
        /// In this case, the external is in fact written as a regular savable object to the same stream.
        /// </summary>
        /// <typeparam name="T">Type of object to write</typeparam>
        /// <param name="name">Name of the value</param>
        /// <param name="value">Savable object</param>
        public void WriteExternal<T>(String name, T value) where T : ISavable {
            if(value == null) {
                _output.Write(NULL_OBJECT);
                return;
            } else {
                if(!(value is T)) {
                    throw new InvalidCastException("Cannot write savable as the requested type.");
                }
                if(DetectRecurse(value)) {
                    throw new InvalidOperationException("Cyclic reference detected.");
                }
                _output.Write(A_OK);
            }

            if(_writeExternals) {
                Type type = typeof(T);
                ExternalWriter writer = FindSuitableWriter(type);
                //Possible to remove the default loader, so skip externalization if this is the case
                if(writer != null) {
                    //Writer should supply the reference that we'll write ourselves
                    ExternalReference extReference = writer.Write(name, value, type);
                    if(extReference != null) {
                        _output.Write(true);
                        WriteSavable<ExternalReference>(null, extReference);
                        return;
                    }
                }
            }

            //Fall back, either not writing externals -or- couldn't find a suitable writer
            _output.Write(false);
            this.Write(null, value.GetType().AssemblyQualifiedName);

            AddRecurse(value);
            value.Write(this);
            RemoveRecurse(value);
        }

        private ExternalWriter FindSuitableWriter(Type type) {
            BinaryExternalWriter defaultWriter = null;
            foreach(KeyValuePair<Type, ExternalWriter> kv in _externalWriters) {
                if(kv.Value is BinaryExternalWriter) {
                    defaultWriter = kv.Value as BinaryExternalWriter;
                    continue;
                }
                Type writerType = kv.Key;
                if(writerType.Equals(type) || writerType.IsAssignableFrom(type)) {
                    return kv.Value;
                }
            }
            return defaultWriter;
        }

        private void AddRecurse(Object value) {
            lock(((IDictionary) _recurseDetection).SyncRoot) {
                _recurseDetection.Add(value, true);
            }
        }

        private void RemoveRecurse(Object value) {
            lock(((IDictionary) _recurseDetection).SyncRoot) {
                _recurseDetection.Remove(value);
            }
        }

        private bool DetectRecurse(Object value) {
            bool recurse = false;
            lock(((IDictionary) _recurseDetection).SyncRoot) {
                recurse = _recurseDetection.ContainsKey(value);
            }
            return recurse;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing) {
            if(!_isDisposed) {
                Flush();
                if(disposing) {
                    _output.Dispose();
                }
            }
            _isDisposed = true;
        }
    }
}
